// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/threading/post_task_and_reply_impl.h"

#include "base/bind.h"
#include "base/location.h"
#include "base/single_thread_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"

namespace base {

namespace {

    // This relay class remembers the MessageLoop that it was created on, and
    // ensures that both the |task| and |reply| Closures are deleted on this same
    // thread. Also, |task| is guaranteed to be deleted before |reply| is run or
    // deleted.
    //
    // If this is not possible because the originating MessageLoop is no longer
    // available, the the |task| and |reply| Closures are leaked.  Leaking is
    // considered preferable to having a thread-safetey violations caused by
    // invoking the Closure destructor on the wrong thread.
    class PostTaskAndReplyRelay {
    public:
        PostTaskAndReplyRelay(const tracked_objects::Location& from_here,
            const Closure& task,
            const Closure& reply)
            : from_here_(from_here)
            , origin_task_runner_(ThreadTaskRunnerHandle::Get())
        {
            task_ = task;
            reply_ = reply;
        }

        ~PostTaskAndReplyRelay()
        {
            DCHECK(origin_task_runner_->BelongsToCurrentThread());
            task_.Reset();
            reply_.Reset();
        }

        void Run()
        {
            task_.Run();
            origin_task_runner_->PostTask(
                from_here_, Bind(&PostTaskAndReplyRelay::RunReplyAndSelfDestruct, base::Unretained(this)));
        }

    private:
        void RunReplyAndSelfDestruct()
        {
            DCHECK(origin_task_runner_->BelongsToCurrentThread());

            // Force |task_| to be released before |reply_| is to ensure that no one
            // accidentally depends on |task_| keeping one of its arguments alive while
            // |reply_| is executing.
            task_.Reset();

            reply_.Run();

            // Cue mission impossible theme.
            delete this;
        }

        tracked_objects::Location from_here_;
        scoped_refptr<SingleThreadTaskRunner> origin_task_runner_;
        Closure reply_;
        Closure task_;
    };

} // namespace

namespace internal {

    bool PostTaskAndReplyImpl::PostTaskAndReply(
        const tracked_objects::Location& from_here,
        const Closure& task,
        const Closure& reply)
    {
        // TODO(tzik): Use DCHECK here once the crash is gone. http://crbug.com/541319
        CHECK(!task.is_null()) << from_here.ToString();
        CHECK(!reply.is_null()) << from_here.ToString();
        PostTaskAndReplyRelay* relay = new PostTaskAndReplyRelay(from_here, task, reply);
        if (!PostTask(from_here, Bind(&PostTaskAndReplyRelay::Run, Unretained(relay)))) {
            delete relay;
            return false;
        }

        return true;
    }

} // namespace internal

} // namespace base
